/*
 * Copyright (c) 2013, Slick2D
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * - Neither the name of the Slick2D nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package org.newdawn.slick.openal;

import org.lwjgl.BufferUtils;
import org.lwjgl.Sys;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.OpenALException;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;

/**
 * Responsible for holding and playing the sounds used in the game.
 *
 * @author Kevin Glass
 * @author Rockstar setVolume cleanup
 */
@SuppressWarnings({"rawtypes", "unchecked", "unused"})
public class SoundStore {

    /**
     * The single instance of this class
     */
    private static org.newdawn.slick.openal.SoundStore
        store = new org.newdawn.slick.openal.SoundStore();

    /**
     * True if sound effects are turned on
     */
    private boolean sounds;
    /**
     * True if music is turned on
     */
    private boolean music;
    /**
     * True if sound initialisation succeeded
     */
    private boolean soundWorks;
    /**
     * The number of sound sources enabled - default 8
     */
    private int sourceCount;
    /**
     * The map of references to IDs of previously loaded sounds
     */
    private HashMap loaded = new HashMap();
    /**
     * The ID of the buffer containing the music currently being played
     */
    private int currentMusic = -1;
    /**
     * The OpenGL AL sound sources in use
     */
    private IntBuffer sources;
    /**
     * The next source to be used for sound effects
     */
    private int nextSource;
    /**
     * True if the sound system has been initialise
     */
    private boolean inited = false;
    /**
     * The MODSound to be updated
     */
    private MODSound mod;
    /**
     * The stream to be updated
     */
    private OpenALStreamPlayer stream;

    /**
     * The global music volume setting
     */
    private float musicVolume = 1.0f;
    /**
     * The global sound fx volume setting
     */
    private float soundVolume = 1.0f;
    /**
     * The volume given for the last current music
     */
    private float lastCurrentMusicVolume = 1.0f;

    /**
     * True if the music is paused
     */
    private boolean paused;
    /**
     * True if we're returning deferred versions of resources
     */
    private boolean deferred;

    /**
     * The buffer used to set the velocity of a source
     */
    private FloatBuffer sourceVel =
        BufferUtils.createFloatBuffer(3).put(new float[] {0.0f, 0.0f, 0.0f});
    /**
     * The buffer used to set the position of a source
     */
    private FloatBuffer sourcePos = BufferUtils.createFloatBuffer(3);

    /**
     * The maximum number of sources
     */
    private int maxSources = 64;

    /**
     * Create a new sound store
     */
    private SoundStore() {
    }

    /**
     * Get the single instance of this class
     *
     * @return The single instnace of this class
     */
    public static org.newdawn.slick.openal.SoundStore get() {
        return store;
    }

    /**
     * Clear out the sound store contents
     */
    public void clear() {
        store = new org.newdawn.slick.openal.SoundStore();
    }

    /**
     * Disable use of the Sound Store
     */
    public void disable() {
        inited = true;
    }

    /**
     * Check if we're using deferred loading
     *
     * @return True if we're loading deferred sounds
     */
    public boolean isDeferredLoading() {
        return deferred;
    }

    /**
     * True if we should only record the request to load in the intention
     * of loading the sound later
     *
     * @param deferred True if the we should load a token
     */
    public void setDeferredLoading(boolean deferred) {
        this.deferred = deferred;
    }

    /**
     * Check if music should currently be playing
     *
     * @return True if music is currently playing
     */
    public boolean isMusicOn() {
        return music;
    }

    /**
     * Inidicate whether music should be playing
     *
     * @param music True if music should be played
     */
    public void setMusicOn(boolean music) {
        if (soundWorks) {
            this.music = music;
            if (music) {
                restartLoop();
                setMusicVolume(musicVolume);
            } else {
                pauseLoop();
            }
        }
    }

    /**
     * Get the volume scalar of the music that is currently playing.
     *
     * @return The volume of the music currently playing
     */
    public float getCurrentMusicVolume() {
        return lastCurrentMusicVolume;
    }

    /**
     * Set the music volume of the current playing music. Does NOT affect the global volume
     *
     * @param volume The volume for the current playing music
     */
    public void setCurrentMusicVolume(float volume) {
        if (volume < 0) {
            volume = 0;
        }
        if (volume > 1) {
            volume = 1;
        }

        if (soundWorks) {
            lastCurrentMusicVolume = volume;
            AL10.alSourcef(sources.get(0), AL10.AL_GAIN, lastCurrentMusicVolume * musicVolume);
        }
    }

    /**
     * Check if sound works at all
     *
     * @return True if sound works at all
     */
    public boolean soundWorks() {
        return soundWorks;
    }

    /**
     * Check if music is currently enabled
     *
     * @return True if music is currently enabled
     */
    public boolean musicOn() {
        return music;
    }

    /**
     * Get the volume for sounds
     *
     * @return The volume for sounds
     */
    public float getSoundVolume() {
        return soundVolume;
    }

    /**
     * Set the sound volume
     *
     * @param volume The volume for sound fx
     */
    public void setSoundVolume(float volume) {
        if (volume < 0) {
            volume = 0;
        }
        soundVolume = volume;
    }

    /**
     * Get the volume for music
     *
     * @return The volume for music
     */
    public float getMusicVolume() {
        return musicVolume;
    }

    /**
     * Set the music volume
     *
     * @param volume The volume for music
     */
    public void setMusicVolume(float volume) {
        if (volume < 0) {
            volume = 0;
        }
        if (volume > 1) {
            volume = 1;
        }

        musicVolume = volume;
        if (soundWorks) {
            AL10.alSourcef(sources.get(0), AL10.AL_GAIN, lastCurrentMusicVolume * musicVolume);
        }
    }

    /**
     * Get the ID of a given source
     *
     * @param index The ID of a given source
     * @return The ID of the given source
     */
    public int getSource(int index) {
        if (!soundWorks) {
            return -1;
        }
        if (index < 0) {
            return -1;
        }
        return sources.get(index);
    }

    /**
     * Indicate whether sound effects should be played
     *
     * @param sounds True if sound effects should be played
     */
    public void setSoundsOn(boolean sounds) {
        if (soundWorks) {
            this.sounds = sounds;
        }
    }

    /**
     * Check if sound effects are currently enabled
     *
     * @return True if sound effects are currently enabled
     */
    public boolean soundsOn() {
        return sounds;
    }

    /**
     * Set the maximum number of concurrent sound effects that will be
     * attempted
     *
     * @param max The maximum number of sound effects/music to mix
     */
    public void setMaxSources(int max) {
        this.maxSources = max;
    }

    /**
     * Initialise the sound effects stored. This must be called
     * before anything else will work
     */
    public void init() {
        if (inited) {
            return;
        }
        Log.info("Initialising sounds..");
        inited = true;

        AccessController.doPrivileged(new PrivilegedAction() {
            @Override
            public Object run() {
                try {
                    AL.create();
                    soundWorks = true;
                    sounds = true;
                    music = true;
                    Log.info("- Sound works");
                } catch (Exception e) {
                    Log.error("Sound initialisation failure.");
                    Log.error(e);
                    soundWorks = false;
                    sounds = false;
                    music = false;
                }

                return null;
            }
        });

        if (soundWorks) {
            sourceCount = 0;
            sources = BufferUtils.createIntBuffer(maxSources);
            while (AL10.alGetError() == AL10.AL_NO_ERROR) {
                IntBuffer temp = BufferUtils.createIntBuffer(1);

                try {
                    AL10.alGenSources(temp);

                    if (AL10.alGetError() == AL10.AL_NO_ERROR) {
                        sourceCount++;
                        sources.put(temp.get(0));
                        if (sourceCount > maxSources - 1) {
                            break;
                        }
                    }
                } catch (OpenALException e) {
                    // expected at the end
                    break;
                }
            }
            Log.info("- " + sourceCount + " OpenAL source available");

            if (AL10.alGetError() != AL10.AL_NO_ERROR) {
                sounds = false;
                music = false;
                soundWorks = false;
                Log.error("- AL init failed");
            } else {
                FloatBuffer listenerOri = BufferUtils.createFloatBuffer(6)
                    .put(new float[] {0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f});
                FloatBuffer listenerVel =
                    BufferUtils.createFloatBuffer(3).put(new float[] {0.0f, 0.0f, 0.0f});
                FloatBuffer listenerPos =
                    BufferUtils.createFloatBuffer(3).put(new float[] {0.0f, 0.0f, 0.0f});
                listenerPos.flip();
                listenerVel.flip();
                listenerOri.flip();
                AL10.alListener(AL10.AL_POSITION, listenerPos);
                AL10.alListener(AL10.AL_VELOCITY, listenerVel);
                AL10.alListener(AL10.AL_ORIENTATION, listenerOri);

                Log.info("- Sounds source generated");
            }
        }
    }

    /**
     * Stop a particular sound source
     *
     * @param index The index of the source to stop
     */
    void stopSource(int index) {
        AL10.alSourceStop(sources.get(index));
    }

    /**
     * Play the specified buffer as a sound effect with the specified
     * pitch and gain.
     *
     * @param buffer The ID of the buffer to play
     * @param pitch  The pitch to play at
     * @param gain   The gain to play at
     * @param loop   True if the sound should loop
     * @return source The source that will be used
     */
    int playAsSound(int buffer, float pitch, float gain, boolean loop) {
        return playAsSoundAt(buffer, pitch, gain, loop, 0, 0, 0);
    }

    /**
     * Play the specified buffer as a sound effect with the specified
     * pitch and gain.
     *
     * @param buffer The ID of the buffer to play
     * @param pitch  The pitch to play at
     * @param gain   The gain to play at
     * @param loop   True if the sound should loop
     * @param x      The x position to play the sound from
     * @param y      The y position to play the sound from
     * @param z      The z position to play the sound from
     * @return source The source that will be used
     */
    int playAsSoundAt(int buffer, float pitch, float gain, boolean loop, float x, float y,
        float z) {
        gain *= soundVolume;
        if (gain == 0) {
            gain = 0.001f;
        }
        if (soundWorks) {
            if (sounds) {
                int nextSource = findFreeSource();
                if (nextSource == -1) {
                    return -1;
                }

                AL10.alSourceStop(sources.get(nextSource));

                AL10.alSourcei(sources.get(nextSource), AL10.AL_BUFFER, buffer);
                AL10.alSourcef(sources.get(nextSource), AL10.AL_PITCH, pitch);
                AL10.alSourcef(sources.get(nextSource), AL10.AL_GAIN, gain);
                AL10.alSourcei(sources.get(nextSource), AL10.AL_LOOPING,
                    loop ? AL10.AL_TRUE : AL10.AL_FALSE);

                sourcePos.clear();
                sourceVel.clear();
                sourceVel.put(new float[] {0, 0, 0});
                sourcePos.put(new float[] {x, y, z});
                sourcePos.flip();
                sourceVel.flip();
                AL10.alSource(sources.get(nextSource), AL10.AL_POSITION, sourcePos);
                AL10.alSource(sources.get(nextSource), AL10.AL_VELOCITY, sourceVel);

                AL10.alSourcePlay(sources.get(nextSource));

                return nextSource;
            }
        }

        return -1;
    }

    /**
     * Check if a particular source is playing
     *
     * @param index The index of the source to check
     * @return True if the source is playing
     */
    boolean isPlaying(int index) {
        int state = AL10.alGetSourcei(sources.get(index), AL10.AL_SOURCE_STATE);

        return (state == AL10.AL_PLAYING);
    }

    /**
     * Find a free sound source
     *
     * @return The index of the free sound source
     */
    private int findFreeSource() {
        for (int i = 1; i < sourceCount - 1; i++) {
            int state = AL10.alGetSourcei(sources.get(i), AL10.AL_SOURCE_STATE);

            if ((state != AL10.AL_PLAYING) && (state != AL10.AL_PAUSED)) {
                return i;
            }
        }

        return -1;
    }

    /**
     * Play the specified buffer as music (i.e. use the music channel)
     *
     * @param buffer The buffer to be played
     * @param pitch  The pitch to play the music at
     * @param gain   The gaing to play the music at
     * @param loop   True if we should loop the music
     */
    void playAsMusic(int buffer, float pitch, float gain, boolean loop) {
        paused = false;

        setMOD(null);

        if (soundWorks) {
            if (currentMusic != -1) {
                AL10.alSourceStop(sources.get(0));
            }

            getMusicSource();

            AL10.alSourcei(sources.get(0), AL10.AL_BUFFER, buffer);
            AL10.alSourcef(sources.get(0), AL10.AL_PITCH, pitch);
            AL10.alSourcei(sources.get(0), AL10.AL_LOOPING, loop ? AL10.AL_TRUE : AL10.AL_FALSE);

            currentMusic = sources.get(0);

            if (!music) {
                pauseLoop();
            } else {
                AL10.alSourcePlay(sources.get(0));
            }
        }
    }

    /**
     * Get the OpenAL source used for music
     *
     * @return The open al source used for music
     */
    private int getMusicSource() {
        return sources.get(0);
    }

    /**
     * Set the pitch at which the current music is being played
     *
     * @param pitch The pitch at which the current music is being played
     */
    public void setMusicPitch(float pitch) {
        if (soundWorks) {
            stream.setup(pitch);
            AL10.alSourcef(sources.get(0), AL10.AL_PITCH, pitch);
        }
    }

    /**
     * Pause the music loop that is currently playing
     */
    public void pauseLoop() {
        if ((soundWorks) && (currentMusic != -1)) {
            paused = true;
            if (stream != null)
                stream.pausing();
            AL10.alSourcePause(currentMusic);
        }
    }

    /**
     * Restart the music loop that is currently paused
     */
    public void restartLoop() {
        if ((music) && (soundWorks) && (currentMusic != -1)) {
            paused = false;
            AL10.alSourcePlay(currentMusic);
            if (stream != null)
                stream.resuming();
        }
    }

    /**
     * Check if the supplied player is currently being polled by this
     * sound store.
     *
     * @param player The player to check
     * @return True if this player is currently in use by this sound store
     */
    boolean isPlaying(OpenALStreamPlayer player) {
        return stream == player;
    }

    /**
     * Get a MOD sound (mod/xm etc)
     *
     * @param ref The refernece to the mod to load
     * @return The sound for play back
     * @throws IOException Indicates a failure to read the data
     */
    public Audio getMOD(String ref) throws IOException {
        return getMOD(ref, ResourceLoader.getResourceAsStream(ref));
    }

    /**
     * Get a MOD sound (mod/xm etc)
     *
     * @param in The stream to the MOD to load
     * @return The sound for play back
     * @throws IOException Indicates a failure to read the data
     */
    public Audio getMOD(InputStream in) throws IOException {
        return getMOD(in.toString(), in);
    }

    /**
     * Get a MOD sound (mod/xm etc)
     *
     * @param ref The stream to the MOD to load
     * @param in  The stream to the MOD to load
     * @return The sound for play back
     * @throws IOException Indicates a failure to read the data
     */
    public Audio getMOD(String ref, InputStream in) throws IOException {
        if (!soundWorks) {
            return new NullAudio();
        }
        if (!inited) {
            throw new RuntimeException(
                "Can't load sounds until SoundStore is init(). Use the container init() method.");
        }
        if (deferred) {
            return new DeferredSound(ref, in, DeferredSound.MOD);
        }

        return new MODSound(this, in);
    }

    /**
     * Get the Sound based on a specified AIF file
     *
     * @param ref The reference to the AIF file in the classpath
     * @return The Sound read from the AIF file
     * @throws IOException Indicates a failure to load the AIF
     */
    public Audio getAIF(String ref) throws IOException {
        return getAIF(ref, ResourceLoader.getResourceAsStream(ref));
    }

    /**
     * Get the Sound based on a specified AIF file
     *
     * @param in The stream to the MOD to load
     * @return The Sound read from the AIF file
     * @throws IOException Indicates a failure to load the AIF
     */
    public Audio getAIF(InputStream in) throws IOException {
        return getAIF(in.toString(), in);
    }

    /**
     * Get the Sound based on a specified AIF file
     *
     * @param ref The reference to the AIF file in the classpath
     * @param in  The stream to the AIF to load
     * @return The Sound read from the AIF file
     * @throws IOException Indicates a failure to load the AIF
     */
    public Audio getAIF(String ref, InputStream in) throws IOException {
        in = new BufferedInputStream(in);

        if (!soundWorks) {
            return new NullAudio();
        }
        if (!inited) {
            throw new RuntimeException(
                "Can't load sounds until SoundStore is init(). Use the container init() method.");
        }
        if (deferred) {
            return new DeferredSound(ref, in, DeferredSound.AIF);
        }

        int buffer = -1;

        if (loaded.get(ref) != null) {
            buffer = ((Integer) loaded.get(ref)).intValue();
        } else {
            try {
                IntBuffer buf = BufferUtils.createIntBuffer(1);

                AiffData data = AiffData.create(in);
                AL10.alGenBuffers(buf);
                AL10.alBufferData(buf.get(0), data.format, data.data, data.samplerate);

                loaded.put(ref, new Integer(buf.get(0)));
                buffer = buf.get(0);
            } catch (Exception e) {
                Log.error(e);
                IOException x = new IOException("Failed to load: " + ref);
                x.initCause(e);

                throw x;
            }
        }

        if (buffer == -1) {
            throw new IOException("Unable to load: " + ref);
        }

        return new AudioImpl(this, buffer);
    }

    /**
     * Get the Sound based on a specified WAV file
     *
     * @param ref The reference to the WAV file in the classpath
     * @return The Sound read from the WAV file
     * @throws IOException Indicates a failure to load the WAV
     */
    public Audio getWAV(String ref) throws IOException {
        return getWAV(ref, ResourceLoader.getResourceAsStream(ref));
    }

    /**
     * Get the Sound based on a specified WAV file
     *
     * @param in The stream to the WAV to load
     * @return The Sound read from the WAV file
     * @throws IOException Indicates a failure to load the WAV
     */
    public Audio getWAV(InputStream in) throws IOException {
        return getWAV(in.toString(), in);
    }

    /**
     * Get the Sound based on a specified WAV file
     *
     * @param ref The reference to the WAV file in the classpath
     * @param in  The stream to the WAV to load
     * @return The Sound read from the WAV file
     * @throws IOException Indicates a failure to load the WAV
     */
    public Audio getWAV(String ref, InputStream in) throws IOException {
        if (!soundWorks) {
            return new NullAudio();
        }
        if (!inited) {
            throw new RuntimeException(
                "Can't load sounds until SoundStore is init(). Use the container init() method.");
        }
        if (deferred) {
            return new DeferredSound(ref, in, DeferredSound.WAV);
        }

        int buffer = -1;

        if (loaded.get(ref) != null) {
            buffer = ((Integer) loaded.get(ref)).intValue();
        } else {
            try {
                IntBuffer buf = BufferUtils.createIntBuffer(1);

                WaveData data = WaveData.create(in);
                AL10.alGenBuffers(buf);
                AL10.alBufferData(buf.get(0), data.format, data.data, data.samplerate);

                loaded.put(ref, new Integer(buf.get(0)));
                buffer = buf.get(0);
            } catch (Exception e) {
                Log.error(e);
                IOException x = new IOException("Failed to load: " + ref);
                x.initCause(e);

                throw x;
            }
        }

        if (buffer == -1) {
            throw new IOException("Unable to load: " + ref);
        }

        return new AudioImpl(this, buffer);
    }

    /**
     * Get the Sound based on a specified OGG file
     *
     * @param ref The reference to the OGG file in the classpath
     * @return The Sound read from the OGG file
     * @throws IOException Indicates a failure to load the OGG
     */
    public Audio getOggStream(String ref) throws IOException {
        if (!soundWorks) {
            return new NullAudio();
        }

        setMOD(null);
        setStream(null);

        if (currentMusic != -1) {
            AL10.alSourceStop(sources.get(0));
        }

        getMusicSource();
        currentMusic = sources.get(0);

        return new StreamSound(new OpenALStreamPlayer(currentMusic, ref));
    }

    /**
     * Get the Sound based on a specified OGG file
     *
     * @param ref The reference to the OGG file in the classpath
     * @return The Sound read from the OGG file
     * @throws IOException Indicates a failure to load the OGG
     */
    public Audio getOggStream(URL ref) throws IOException {
        if (!soundWorks) {
            return new NullAudio();
        }

        setMOD(null);
        setStream(null);

        if (currentMusic != -1) {
            AL10.alSourceStop(sources.get(0));
        }

        getMusicSource();
        currentMusic = sources.get(0);

        return new StreamSound(new OpenALStreamPlayer(currentMusic, ref));
    }

    /**
     * Get the Sound based on a specified OGG file
     *
     * @param ref The reference to the OGG file in the classpath
     * @return The Sound read from the OGG file
     * @throws IOException Indicates a failure to load the OGG
     */
    public Audio getOgg(String ref) throws IOException {
        return getOgg(ref, ResourceLoader.getResourceAsStream(ref));
    }

    /**
     * Get the Sound based on a specified OGG file
     *
     * @param in The stream to the OGG to load
     * @return The Sound read from the OGG file
     * @throws IOException Indicates a failure to load the OGG
     */
    public Audio getOgg(InputStream in) throws IOException {
        return getOgg(in.toString(), in);
    }

    /**
     * Get the Sound based on a specified OGG file
     *
     * @param ref The reference to the OGG file in the classpath
     * @param in  The stream to the OGG to load
     * @return The Sound read from the OGG file
     * @throws IOException Indicates a failure to load the OGG
     */
    public Audio getOgg(String ref, InputStream in) throws IOException {
        if (!soundWorks) {
            return new NullAudio();
        }
        if (!inited) {
            throw new RuntimeException(
                "Can't load sounds until SoundStore is init(). Use the container init() method.");
        }
        if (deferred) {
            return new DeferredSound(ref, in, DeferredSound.OGG);
        }

        int buffer = -1;

        if (loaded.get(ref) != null) {
            buffer = ((Integer) loaded.get(ref)).intValue();
        } else {
            try {
                IntBuffer buf = BufferUtils.createIntBuffer(1);

                OggDecoder decoder = new OggDecoder();
                OggData ogg = decoder.getData(in);

                AL10.alGenBuffers(buf);
                AL10.alBufferData(buf.get(0),
                    ogg.channels > 1 ? AL10.AL_FORMAT_STEREO16 : AL10.AL_FORMAT_MONO16, ogg.data,
                    ogg.rate);

                loaded.put(ref, new Integer(buf.get(0)));

                buffer = buf.get(0);
            } catch (Exception e) {
                Log.error(e);
                Sys.alert("Error", "Failed to load: " + ref + " - " + e.getMessage());
                throw new IOException("Unable to load: " + ref);
            }
        }

        if (buffer == -1) {
            throw new IOException("Unable to load: " + ref);
        }

        return new AudioImpl(this, buffer);
    }

    /**
     * Set the mod thats being streamed if any
     *
     * @param sound The mod being streamed
     */
    void setMOD(MODSound sound) {
        if (!soundWorks) {
            return;
        }

        currentMusic = sources.get(0);
        stopSource(0);

        this.mod = sound;
        if (sound != null) {
            this.stream = null;
        }
        paused = false;
    }

    /**
     * Set the stream being played
     *
     * @param stream The stream being streamed
     */
    void setStream(OpenALStreamPlayer stream) {
        if (!soundWorks) {
            return;
        }

        if (this.stream != null && this.stream != stream) {
            this.stream.close();
        }
        currentMusic = sources.get(0);
        this.stream = stream;
        if (stream != null) {
            this.mod = null;
        }
        paused = false;
    }

    /**
     * Poll the streaming system
     *
     * @param delta The amount of time passed since last poll (in milliseconds)
     */
    public void poll(int delta) {
        if (!soundWorks) {
            return;
        }
        if (paused) {
            return;
        }

        if (music) {
            if (mod != null) {
                try {
                    mod.poll();
                } catch (OpenALException e) {
                    Log.error("Error with OpenGL MOD Player on this this platform");
                    Log.error(e);
                    mod = null;
                }
            }
            if (stream != null) {
                try {
                    stream.update();
                } catch (OpenALException e) {
                    Log.error("Error with OpenGL Streaming Player on this this platform");
                    Log.error(e);
                    mod = null;
                }
            }
        }
    }

    /**
     * Check if the music is currently playing
     *
     * @return True if the music is playing
     */
    public boolean isMusicPlaying() {
        if (!soundWorks) {
            return false;
        }

        int state = AL10.alGetSourcei(sources.get(0), AL10.AL_SOURCE_STATE);
        return ((state == AL10.AL_PLAYING) || (state == AL10.AL_PAUSED));
    }

    /**
     * Stop a playing sound identified by the ID returned from playing. This utility method
     * should only be used when needing to stop sound effects that may have been played
     * more than once and need to be explicitly stopped.
     *
     * @param id The ID of the underlying OpenAL source as returned from playAsSoundEffect
     */
    public void stopSoundEffect(int id) {
        AL10.alSourceStop(id);
    }

    /**
     * Retrieve the number of OpenAL sound sources that have been
     * determined at initialisation.
     *
     * @return The number of sources available
     */
    public int getSourceCount() {
        return sourceCount;
    }
}
